#!/usr/bin/perl
$Version  = 'SCST Configurator v1.0.11';

# Configures SCST
#
# Author:       Mark R. Buechler
# License:      GPLv2
# Copyright (c) 2005-2009 Mark R. Buechler

sub usage
  {
    die <<"EndUsage";
$Version

Usage:
General Operations
     -config <config>     : Configure SCST given the specified configuration file.
     -ClearConfig         : Clear all SCST configuration.
     -WriteConfig <file>  : Writes the current configuration out to the specified file.
     -checkConfig <file>  : Checks the saved configuration in the specified file.
     -sessions            : List current initiator sessions.
     
Target Driver Operations
     -enable <wwn|host>   : Enable target mode for driver at specified WWN or host.
     -disable <wwn|host>  : Disable target mode for driver at specified WWN or host.
     -issuelip            : Issue LIP on all target-enabled FC fabrics.

Device Operations
     -adddev <device>     : Adds a device to a handler.
         -handler <handler>
         -path <path>
         -options <options>
         -blocksize <bytes>
     -resyncdev <device>  : Resync the size of a device with the initiator(s).
         -handler <handler>
     -RemoveDev <device>  : Remove a device from a handler.
         -handler <handler>
     -SetT10DeviceId <id> : Sets the T10 Device ID of a device.
         -device <device>
         -handler <handler>
 
User Operations
     -adduser <user>      : Adds a user to a security group.
         -group <group>
     -MoveUser <user>     : Moves a user from one security group to another.
         -group <group 1>
         -to <group 2>
     -RemoveUser <user>   : Delete a user from a security group.
         -group <group>
     -ClearUsers          : Clear all users from a given security group.
         -group <group>

Group Operations
     -addgroup <group>    : Add a given group to available security groups.
     -renamegroup <group> : Renames a give group to a new name.
         -to <new group>
     -RemoveGroup <group> : Remove a give group from available security groups.
     
Assignment Operations
     -assigndev <device>  : Assign a given device to a security group.
         -group <group>
         -lun <lun>
         -options <options>
     -ReplaceDev <new dev>: Replaces a device assigned to a give LUN and group.
         -group <group>
         -lun <lun>
         -options <options>
     -ReleaseDev <device> : Remove a given device from a security group.
         -group <group>
     -ClearDevs           : Clear all device assignments for a security group.
         -group <group>

Options
     -ForceConfig         : Force all configuration changes, even deletions (DANGER!).
     -noprompt            : Do not prompt or pause. Use with caution!
  
Debugging (limited support)
     -debug               : Debug mode - don\'t do anything destructive.

Available Handlers:
      disk, vdisk, disk_perf, cdrom, vcdrom, changer, modisk, modisk_perf, tape, tape_perf

Available Options for create and open:
      WRITE_THROUGH, READ_ONLY, O_DIRECT, NULLIO, NV_CACHE, BLOCK_IO, REMOVABLE

Available Options for assign and replace:
      READ_ONLY

Examples:
     Enable target mode for fibre card specifying its WWN
       scstadmin -enable 50:06:0B:00:00:39:71:78

     Disable target mode for SCSI host specifying host number
       scstadmin -disable host4 

     Create a new security group:
       scstadmin -addgroup HOST01
       
     Create a device given an already existing disk file:
       scstadmin -adddev DISK01 -handler vdisk -path /vdisks/disk01.dsk -options READ_ONLY,WRITE_THROUGH

     Setting the T10 Device ID of a device
       scstadmin -SetT10DeviceId test_disk -device disk1 -handler vdisk
       
     Assign a device to a security group:
       scstadmin -assigndev DISK01 -group HOST01 -lun 1

     Rename a security group:
       scstadmin -RenameGroup HOST01 -to SERVER01

     Tell all initiators to rescan LUNs:
       scstadmin -issuelip

EndUsage
  }

use SCST::SCST 0.8.22;
use Getopt::Long;
use IO::File;
use IO::Dir;
use POSIX;
use strict;

my $_DEF_CONFIG_ = '/etc/scst.conf';

my $TRUE  = 1;
my $FALSE = 0;

my $_MAX_LUNS_       = 255;
my $_DEFAULT_GROUP_  = 'Default';

my $_SCSI_CLASS_     = '/sys/class/scsi_host';
my $_FC_CLASS_       = '/sys/class/fc_host';
my $_SCSI_ISP_       = '/proc/scsi/isp';
my $_SCSITGT_QLAISP_ = '/proc/scsi_tgt/qla_isp';

my $_TGT_DEF_PREFIX_ = 'Default_';
my $_TGT_TMP_PREFIX_ = 'TMP_GRP';

my $SCST;
my $DEVICES;
my $TARGETS;
my %USERS;
my %ASSIGNMENTS;
my %HANDLERS;
my %GROUPS;
my $_DEBUG_;

my %_HANDLER_MAP_ = ('cdrom' => $SCST::SCST::CDROM_TYPE,
		     'changer' => $SCST::SCST::CHANGER_TYPE,
		     'disk' => $SCST::SCST::DISK_TYPE,
		     'vdisk' => $SCST::SCST::VDISK_TYPE,
		     'vcdrom' => $SCST::SCST::VCDROM_TYPE,
		     'disk_perf' => $SCST::SCST::DISKPERF_TYPE,
		     'modisk' => $SCST::SCST::MODISK_TYPE,
		     'modisk_perf' => $SCST::SCST::MODISKPERF_TYPE,
		     'tape' => $SCST::SCST::TAPE_TYPE,
		     'tape_perf' => $SCST::SCST::TAPEPERF_TYPE,
		     'processor' => $SCST::SCST::PROCESSOR_TYPE,
		# Add in the dev_ names as well
		     'dev_cdrom' => $SCST::SCST::CDROM_TYPE,
		     'dev_changer' => $SCST::SCST::CHANGER_TYPE,
		     'dev_disk' => $SCST::SCST::DISK_TYPE,
		     'dev_disk_perf' => $SCST::SCST::DISKPERF_TYPE,
		     'dev_modisk' => $SCST::SCST::MODISK_TYPE,
		     'dev_modisk_perf' => $SCST::SCST::MODISKPERF_TYPE,
		     'dev_tape' => $SCST::SCST::TAPE_TYPE,
		     'dev_tape_perf' => $SCST::SCST::TAPEPERF_TYPE,
		     'dev_processor' => $SCST::SCST::PROCESSOR_TYPE);
		     
my %_REVERSE_MAP_ = ($SCST::SCST::CDROM_TYPE => 'cdrom',
		     $SCST::SCST::CHANGER_TYPE => 'changer',
		     $SCST::SCST::DISK_TYPE => 'disk',
		     $SCST::SCST::VDISK_TYPE => 'vdisk',
		     $SCST::SCST::VCDROM_TYPE => 'vcdrom',
		     $SCST::SCST::DISKPERF_TYPE => 'disk_perf',
		     $SCST::SCST::MODISK_TYPE => 'modisk',
		     $SCST::SCST::MODISKPERF_TYPE => 'modisk_perf',
		     $SCST::SCST::TAPE_TYPE => 'tape',
		     $SCST::SCST::TAPEPERF_TYPE => 'tape_perf',
		     $SCST::SCST::PROCESSOR_TYPE => 'processor');

my %_HANDLER_TYPE_MAP_ = ($SCST::SCST::IOTYPE_PHYSICAL => 'physical',
			  $SCST::SCST::IOTYPE_VIRTUAL => 'virtual',
			  $SCST::SCST::IOTYPE_PERFORMANCE => 'performance');

$SIG{INT} = \&commitSuicide;

use vars qw($Version);

POSIX::setsid();

&main();

sub getArgs {
	my $applyConfig;
	my $forceConfig;
	my $clearConfig;
	my $writeConfig;
	my $checkConfig;
	my $showSessions;
	my $addDev;
	my $devPath;
	my $resyncDev;
	my $replaceDev;
	my $removeDev;
	my $sett10id;
	my $addUser;
	my $moveUser;
	my $removeUser;
	my $clearUsers;
	my $addGroup;
	my $toGroup;
	my $renameGroup;
	my $removeGroup;
	my $assignDev;
	my $replaceDev;
	my $releaseDev;
	my $clearDevs;
	my $devLun;
	my $handler;
	my $device;
	my $group;
	my $options;
	my $blocksize;
	my $enable;
	my $disable;
	my $issuelip;
	my $noprompt;

	my $p = new Getopt::Long::Parser;

	if (!$p->getoptions('config:s'          => \$applyConfig,
			    'ClearConfig'       => \$clearConfig,
			    'ForceConfig'	=> \$forceConfig,
			    'WriteConfig=s'	=> \$writeConfig,
			    'checkConfig=s'	=> \$checkConfig,
			    'sessions'		=> \$showSessions,
			    'adddev=s'          => \$addDev,
			    'path=s'		=> \$devPath,
			    'ReplaceDev=s'	=> \$replaceDev,
			    'RemoveDev=s'       => \$removeDev,
			    'SetT10DeviceId=s'	=> \$sett10id,
			    'lun=s'             => \$devLun,
			    'adduser=s'         => \$addUser,
			    'MoveUser=s'	=> \$moveUser,
			    'RemoveUser=s'      => \$removeUser,
			    'ClearUsers'        => \$clearUsers,
			    'addgroup=s'        => \$addGroup,
			    'to=s'		=> \$toGroup,
			    'RemoveGroup=s'     => \$removeGroup,
			    'renamegroup=s'	=> \$renameGroup,
			    'assigndev=s'       => \$assignDev,
			    'resyncdev=s'	=> \$resyncDev,
			    'ReleaseDev=s'      => \$releaseDev,
			    'ClearDevs'         => \$clearDevs,
			    'handler=s'         => \$handler,
			    'device=s'		=> \$device,
			    'group=s'           => \$group,
			    'options=s'		=> \$options,
			    'blocksize=s'	=> \$blocksize,
			    'enable=s'		=> \$enable,
			    'disable=s'		=> \$disable,
			    'issuelip'		=> \$issuelip,
			    'noprompt'		=> \$noprompt,
			    'debug'             => \$_DEBUG_)) {
		&usage();
	}

	if ((defined($enable) && !$enable) || (defined($disable) && !$disable)) {
		print "Argument -enable/-disable requires a WWN or host.\n\n";
		usage();
	}

	if (defined($issuelip) && ($enable || $disable)) {
		print "Argument -issuelip cannot be used with -enable or -disable.\n\n";
		usage();
	}

	if ($handler && !$_HANDLER_MAP_{$handler}) {
		print "Invalid handler '$handler' specified. Available handlers are:\n\n";
		foreach my $_handler (keys %_HANDLER_MAP_) {
			print "\t$_handler\n";
		}
		print "\n";
		exit 1;
	}

	if ($addDev && !($handler && $devPath)) {
		print "Please specify -handler and -path with -adddev.\n\n";
		usage();
	}

	if (defined($blocksize) && !$blocksize) {
		print "Please specify bytes with -blocksize.\n\n";
		usage();
	}

	if ($blocksize && !$addDev) {
		print "Please specify -adddev with -blocksize.\n";
		usage();
	}

	if (defined($forceConfig) && !defined($applyConfig)) {
		print "Please specify -config with -ForceConfig.\n\n";
		usage();
	}

	if ($resyncDev && !$handler) {
		print "Please specify -handler with -resyncdev.\n\n";
		usage();
	}

	if ($removeDev && !$handler) {
		print "Please specify -handler with -RemoveDev.\n\n";
		usage();
	}

	if ($addUser && !defined($group)) {
		print "Please specify -group with -adduser.\n\n";
		usage();
	}

	if ($moveUser && (!defined($group) || !defined($toGroup))) {
		print "Please specify -group and -to with -MoveUser.\n\n";
		usage();
	}

	if ($removeUser && !defined($group)) {
		print "Please specify -group with -RemoveUser.\n\n";
		usage();
	}

	if ($clearUsers && !defined($group)) {
		print "Please specify -group with -ClearUsers.\n\n";
		usage();
	}

	if ($renameGroup && !defined($toGroup)) {
		print "Please specify -to with -renamegroup.\n\n";
		usage();
	}

	if ($assignDev && !defined($group)) {
		print "Please specify -group with -assigndev.\n\n";
		usage();
	}

	if ($replaceDev && (!defined($group) || !defined($devLun))) {
		print "Please specify -group and -lun with -ReplaceDev.\n\n";
		usage();
	}

	if ($releaseDev && !defined($group)) {
		print "Please specify -group with -ReleaseDev.\n\n";
		usage();
	}

	if ($sett10id && (!$device || !$handler)) {
		print "Please specify -device and -handler with -SetT10DeviceId.\n\n";
		usage();
	}

	if ($clearDevs && !defined($group)) {
		print "Please specify -group with -ClearDevs.\n\n";
		usage();
	}

	if (defined($writeConfig) && !$writeConfig) {
		print "Please specify a file name to write configuration to..\n\n";
		usage();
	}

	$_DEBUG_ = $TRUE if (defined($_DEBUG_));

	$forceConfig = $TRUE if (defined($forceConfig));
	$showSessions = $TRUE if (defined($showSessions));
	$issuelip = $TRUE if (defined($issuelip));
	$noprompt = $TRUE if (defined($noprompt));

	$enable =~ tr/A-Z/a-z/; $disable =~ tr/A-Z/a-z/;
	$options =~ tr/a-z/A-Z/ if ($options);

	if ((defined($showSessions) + defined($addDev) + defined($resyncDev) +
	     defined($removeDev)+ defined($addUser) + defined($enable) + defined($disable) +
	     defined($removeUser) + defined($clearUsers) + defined($assignDev) +
	     defined($releaseDev) + defined($clearDevs) + defined($applyConfig) +
	     defined($clearConfig) + defined($writeConfig) + defined($checkConfig) +
	     defined($sett10id)) > 1) {
		print "Please specify only one operation at a time.\n";
		usage();
	}

	$applyConfig = $_DEF_CONFIG_ if (defined($applyConfig) && !$applyConfig);
	$checkConfig = $_DEF_CONFIG_ if (defined($checkConfig) && !$checkConfig);

	return ($enable, $disable, $issuelip, $addDev, $devPath, $devLun, $resyncDev, $removeDev, $addUser,
		$moveUser, $removeUser, $clearUsers, $addGroup, $renameGroup, $toGroup, $removeGroup,
		$assignDev, $replaceDev, $releaseDev, $sett10id, $clearDevs, $handler, $device, $group,
		$options, $blocksize, $applyConfig, $forceConfig, $clearConfig, $writeConfig, $checkConfig,
		$showSessions, $noprompt);
}

sub main {
	my $rc;

	STDOUT->autoflush(1);

	# We need to run as root
	if ( $> ) {die("This program must run as root.\n");}

	my ($enable, $disable, $issuelip, $addDev, $devPath, $devLun, $resyncDev, $removeDev, $addUser,
	    $moveUser, $removeUser, $clearUsers, $addGroup, $renameGroup, $toGroup, $removeGroup,
	    $assignDev, $replaceDev, $releaseDev, $sett10id, $clearDevs, $handler, $device, $group,
	    $options, $blocksize, $applyConfig, $forceConfig, $clearConfig, $writeConfig, $checkConfig,
	    $showSessions, $noprompt) = getArgs();

	$SCST = new SCST::SCST($_DEBUG_);

	readWorkingConfig();

	SWITCH: {
		$applyConfig && do {
			if ($forceConfig) {
				$rc = applyConfiguration($applyConfig, $TRUE, $TRUE);
				die("Configuration errors found, aborting.\n") if ($rc);

				if (!$noprompt) {
					print "\nConfiguration will apply in 10 seconds, type ctrl-c to abort..\n";
					sleep 10;
				}
			}

			readWorkingConfig();
			$rc = applyConfiguration($applyConfig, $forceConfig, $FALSE);
			last SWITCH;
		};
		$checkConfig && do {
			$rc = applyConfiguration($checkConfig, $FALSE, $TRUE);
			last SWITCH;
		};
		$writeConfig && do {
			$rc = writeConfiguration($writeConfig);
			last SWITCH;
		};
		$showSessions && do {
			$rc = showSessions();
			last SWITCH;
		};
		defined($clearConfig) && do {
			$rc = clearConfiguration($noprompt);
			last SWITCH;
		};
		$addDev && do {
			$rc = addDevice($handler, $addDev, $devPath, $options, $blocksize);
			last SWITCH;
		};
		$resyncDev && do {
			$rc = resyncDevice($handler, $resyncDev);
			last SWITCH;
		};
		$removeDev && do {
			$rc = removeDevice($handler, $removeDev);
			last SWITCH;
		};
		$sett10id && do {
			$rc = setT10DeviceId($handler, $device, $sett10id);
			last SWITCH;
		};
		$addUser && do {
			$rc = addUser($group, $addUser);
			last SWITCH;
		};
		$moveUser && do {
			$rc = moveUser($group, $moveUser, $toGroup);
			last SWITCH;
		};
		$removeUser && do {
			$rc = removeUser($group, $removeUser);
			last SWITCH;
		};
		defined($clearUsers) && do {
			$rc = clearUsers($group);
			last SWITCH;
		};
		$addGroup && do {
			$rc = addGroup($addGroup);
			last SWITCH;
		};
		$renameGroup && do {
			$rc = renameGroup($renameGroup, $toGroup);
			last SWITCH;
		};
		$removeGroup && do {
			$rc = removeGroup($removeGroup);
			last SWITCH;
		};
		$assignDev && do {
			$rc = assignDevice($group, $assignDev, $devLun, $options);
			last SWITCH;
		};
		$replaceDev && do {
			$rc = replaceDevice($group, $replaceDev, $devLun, $options);
			last SWITCH;
		};
		$releaseDev && do {
			$rc = releaseDevice($group, $releaseDev);
			last SWITCH;
		};
		defined($clearDevs) && do {
			$rc = clearDevices($group);
			last SWITCH;
		};
		$enable && do {
			$enable = unformatTarget($enable);
			$rc = enableTarget($enable, $TRUE);
			last SWITCH;
		};
		$disable && do {
			$disable = unformatTarget($disable);
			$rc = enableTarget($disable, $FALSE);
			last SWITCH;
		};
		$issuelip && do {
			$rc = issueLIP();
			last SWITCH;
		};

		print "No valid operations specified.\n";
		usage();
		exit $TRUE;
	}

	print "\nAll done.\n";

	exit $rc;
}

sub readWorkingConfig {
	my %empty;

	print "Collecting current configuration: ";

	$TARGETS  = undef;
	$DEVICES  = undef;
	%HANDLERS = ();
	%GROUPS   = ();
	%USERS    = ();

	my $eHandlers = $SCST->handlers();

	immediateExit($SCST->errorString());

	foreach my $handler (@{$eHandlers}) {
		$HANDLERS{$handler}++; # For quick lookups
	}

	$TARGETS = targets();

	$DEVICES = $SCST->devices();
	immediateExit($SCST->errorString());

	my $_eGroups = $SCST->groups();
	immediateExit($SCST->errorString());

	foreach my $group (@{$_eGroups}) {
		$GROUPS{$group}++;
		$ASSIGNMENTS{$group} = $SCST->groupDevices($group);
		my $eUsers = $SCST->users($group);

		foreach my $user (@{$eUsers}) {
			$USERS{$group}->{$user}++; # For quick lookups
		}
		$USERS{$group} = \%empty if (!$USERS{$group});
	}

	print "done.\n\n";
}

sub writeConfiguration {
	my $file = shift;
	my $keep_config;

	my $config = readConfig($file, $TRUE);

	if (-f $file) {
		if (!unlink $file) {
			print "Failed to save current configuration, specified ".
			  "file exists and cannot be deleted.\n";
			return 1;
		}
	}

	my $io = new IO::File $file, O_CREAT|O_WRONLY;

	if (!$io) {
		print "Failed to save configuration to file '$file': $!\n";
		return 1;
	}

	print "Writing current configuration to file '$file'.. ";

	print $io "# Automatically generated by $Version.\n\n";

	print $io "# NOTE: Options are pipe (|) seperated.\n\n";

	print $io "[OPTIONS]\n";
	print $io "#OPTION <1|0|YES|NO|TRUE|FALSE|VALUE>\n";
	print $io "# Copy configuration options during a -writeconfig\n";
	print $io "KEEP_CONFIG ";

	if (defined($config)) {
		$keep_config = $$config{'OPTIONS'}->{'default'}->{'KEEP_CONFIG'}[0];
	} else {
		$keep_config = $FALSE;
	}

	if (!defined($config)) {
		print $io "TRUE\n";
	} elsif (!$keep_config ) {
		print $io "FALSE\n";
	} else {
		print $io "TRUE\n";
	}

	print $io "# For FC targets, issue a LIP after every assignment change\n";
	print $io "ISSUE_LIP ";

	if (!$keep_config) {
		print $io "FALSE\n";
	} else {
		print $io ($$config{'OPTIONS'}->{'default'}->{'ISSUE_LIP'}[0] ? "TRUE\n" : "FALSE\n");
	}

	print $io "\n";		

	# Device information
	foreach my $handler (sort keys %HANDLERS) {
		if ($SCST->handlerType($handler) == $SCST::SCST::IOTYPE_VIRTUAL) {
			print $io "[HANDLER ".$_REVERSE_MAP_{$handler}."]\n";
			print $io "#DEVICE <vdisk name>,<device path>";
			if ($handler == $SCST::SCST::VDISK_TYPE) {
				print $io ",<options>,<block size>,<t10 device id>\n";
			} else {
				print $io "\n";
			}

			my $devices = $SCST->handlerDevices($handler);

			immediateExit($SCST->errorString());

			foreach my $device (sort keys %{$devices}) {
				my $options = $$devices{$device}->{'OPTIONS'};

				$options =~ s/\,/\|/g;

				print $io "DEVICE $device,".$$devices{$device}->{'PATH'};
				print $io ",$options";
				print $io ",".$$devices{$device}->{'BLOCKSIZE'};
				print $io ",".$$devices{$device}->{'T10_DEVICE_ID'};
				print $io "\n";
			}

			print $io "\n";
		}
	}

	# User configuration
	foreach my $group (sort keys %USERS) {
		print $io "[GROUP $group]\n";
		print $io "#USER <user wwn>\n";

		foreach my $user (keys %{$USERS{$group}}) {
			print $io "USER $user\n";
		}

		print $io "\n";
	}

	# Assignments configuration
	foreach my $group (sort keys %ASSIGNMENTS) {
		print $io "[ASSIGNMENT $group]\n";
		print $io "#DEVICE <device name>,<lun>,<options>\n";

		my $pointer = $ASSIGNMENTS{$group};
		foreach my $device (sort keys %{$pointer}) {
			print $io "DEVICE $device,".$$pointer{$device}."\n";
		}

		print $io "\n";
	}

	# Targets configuration
	foreach my $type ('enable', 'disable') {
		print $io "[TARGETS $type]\n";
		print $io "#HOST <wwn identifier>\n";

		foreach my $target (sort keys %{$TARGETS}) {
			if ((($type eq 'enable') && $$TARGETS{$target}->{'enabled'}) ||
			    (($type eq 'disable') && !$$TARGETS{$target}->{'enabled'})) {
				my $f_target = formatTarget($target);
				print $io "HOST $f_target\n" if (!$$TARGETS{$target}->{'duplicate'});
			}
		}

		print $io "\n";
	}

	print "done\n";

	close $io;

	return 0;
}

sub applyConfiguration {
	my $confile = shift;
	my $force = shift;
	my $check = shift;
	my $config = readConfig($confile);
	my $errs;
	my $changes = 0;

	my $assign_changes = 0;
	my $targets_changed = $FALSE;

	my %used_devs;
	my %used_users;
	my %used_assignments;
	my %empty;

	my %seen_users;

	my %rename_group;

	my $issue_lip = $$config{'OPTIONS'}->{'default'}->{'ISSUE_LIP'}[0];

	# Cache device/handler configuration
	foreach my $entry (keys %{$$config{'HANDLER'}}) {
		foreach my $device (@{$$config{'HANDLER'}->{$entry}->{'DEVICE'}}) {
			my($vname, undef) = split(/\,/, $device, 2);
			$vname = cleanupString($vname);
			$used_devs{$vname} = $entry;
		}
	}

	# Cache user/group configuration
	foreach my $group (keys %{$$config{'GROUP'}}) {
		foreach my $user (@{$$config{'GROUP'}->{$group}->{'USER'}}) {
			if (defined($seen_users{$user})) {
				print "\t-> FATAL: Configuration invalid. User '$user' is in more ".
				  "than one group!\n";
				exit 1;
			}
			$used_users{$group}->{$user}++;
			$seen_users{$user}++;
		}
		$used_users{$group} = \%empty if (!$used_users{$group});
	}

	# Cache device association configuration
	foreach my $group (keys %{$$config{'ASSIGNMENT'}}) {
		my %seen_luns;

		foreach my $device (@{$$config{'ASSIGNMENT'}->{$group}->{'DEVICE'}}) {
			my($vname, $arg) = split(/\,/, $device, 2);
			$vname = cleanupString($vname);
			my($lun, $options) = split(/\,/, $arg);
			if ($seen_luns{$lun}) {
				print "\t-> FATAL: Configuration invalid. Group '$group' has multiple ".
				  "devices assigned to LUN $lun.\n";
				exit 1;
			}
			$used_assignments{$group}->{$vname} = $arg;
			$seen_luns{$lun}++;
		}
	}

	# If -ForceConfig is used, check for configurations which we've deleted but are still active.
	if ($force || $check) {
		# Associations
		foreach my $group (sort keys %ASSIGNMENTS) {
			if (!defined($used_assignments{$group}) && (keys %{$ASSIGNMENTS{$group}})) {
				print "\t-> WARNING: Group '$group' has no associations in saved configuration";

				if (!$check) {
					print ", clearing all associations.\n";
					if (clearDevices($group)) {
						$errs++;
					} else {
						$changes++;
						$assign_changes++;
					}
				} else {
					print ".\n";
					$changes++;
				}
			} else {
				my $_assignments = $ASSIGNMENTS{$group};

				foreach my $device (sort keys %{$_assignments}) {
					if (!defined($used_assignments{$group}->{$device}) ||
					   ($$_assignments{$device} != $used_assignments{$group}->{$device})) {
						if (defined($used_assignments{$group}->{$device}) &&
						    ($$_assignments{$device} != $used_assignments{$group}->{$device})) {
							print "\t-> WARNING: Device '$device' assigned to group '$group' ".
							  "is at LUN ".$used_assignments{$group}->{$device}.
							  " whereas working configuration reflects LUN ".$$_assignments{$device}; 
						} else {
							print "\t-> WARNING: Device '$device' is not associated with group ".
							  "'$group' in saved configuration";
						}

						if (!$check) {
							my $_lun = $$_assignments{$device};
							my $replace_dev = findAssignedLun($used_assignments{$group}, $_lun);

							if (defined($replace_dev) && ($replace_dev ne $device)) {
								print ", replacing with device '$replace_dev'.\n";

								if (replaceDevice($group, $replace_dev, $_lun)) {
									$errs++;
								} else {
									$changes++;
									$assign_changes++;
								}
							} else {
								print ", releasing.\n";
								if (releaseDevice($group, $device)) {
									$errs++;
								} else {
									$changes++;
									$assign_changes++;
								}
							}
						} else {
							print ".\n";
							$changes++;
						}
					}
				}
			}
		}

		# Users & Groups
		foreach my $group (sort keys %USERS) {
			next if ($group eq $_DEFAULT_GROUP_);
			if (!defined($used_users{$group})) {
				print "\t-> WARNING: Group '$group' does not exist in saved configuration";

				if (!$check) {
					print ", removing.\n";
					if (clearUsers($group)) {
						$errs++;
					} else {
						$changes++;
					}

					if (removeGroup($group)) {
						$errs++;
					} else {
						$changes++;
					}
				} else {
					print ".\n";
					$changes++;
				}
			} else {
				foreach my $user (sort keys %{$USERS{$group}}) {
					if (!defined($used_users{$group}->{$user})) {
						print "\t-> WARNING: User '$user' is not defined as part of group '$group' ".
						  "in saved configuration";

						if (!$check) {
							# Are we moving this user to another group?
							my $new_group = findUserGroup($user, $config);
							if ($new_group && ($new_group ne $group)) {
								print ", moving to group '$new_group'.\n";
								if (moveUser($group, $user, $new_group)) {
									$errs++;
								} else {
									$changes++;
								}
							} else {
								print ", removing.\n";
								if (removeUser($group, $user)) {
									$errs++;
								} else {
									$changes++;
								}
							}
						} else {
							print ".\n";
							$changes++;
						}
					}
				}
			}
		}

		# Devices
		foreach my $device (sort keys %{$DEVICES}) {
			if ($$DEVICES{$device} && !defined($used_devs{$device})) {
				# Device gone, but is it still assigned to a group?
				my $isAssigned = $FALSE;
				foreach my $group (sort keys %used_assignments) {
					if (defined($used_assignments{$group}->{$device})) {
						print "\t-> WARNING: Device '$device' is not defined in saved configuration, ".
						  "however, it is still assigned to group '$group'! Ignoring removal.\n";
						$isAssigned = $TRUE;
					}
				}

				if (!$isAssigned && ($SCST->handlerType($$DEVICES{$device}) == $SCST::SCST::IOTYPE_VIRTUAL)) {
					print "\t-> WARNING: Device '$device' is not defined in saved configuration";

					if (!$check) {
						print ", removing.\n";
						if (removeDevice($_REVERSE_MAP_{$$DEVICES{$device}}, $device)) {
							$errs++;
						} else {
							$changes++;
						}
					} else {
						print ".\n";
						$changes++;
					}
				}
			} else {
				# Handler change
				if ($_HANDLER_MAP_{$used_devs{$device}} != $$DEVICES{$device}) {
					my $handler = $used_devs{$device};

					if ($HANDLERS{$_HANDLER_MAP_{$handler}}) {
						print "\t-> WARNING: Device '$device' changes handler to '$handler'";

						if (!$check) {
							print ", changing.\n";
							if ($SCST->assignDeviceToHandler($device,
											 $_HANDLER_MAP_{$handler})) {
								$errs++;
							} else {
								$changes++;
							}
						} else {
							print ".\n";
							$changes++;
						}
					}
				}
			}
		}
	}

	print "\nApplying configuration additions..\n" if (!$check);
	print "\n";

	readWorkingConfig() if ($force);

	foreach my $_handler (sort keys %{$$config{'HANDLER'}}) {
		if (!$HANDLERS{$_HANDLER_MAP_{$_handler}}) {
			print "\t-> WARNING: Handler '$_handler' does not exist.\n";
			$errs += 1;
			next;
		}

		foreach my $device (@{$$config{'HANDLER'}->{$_handler}->{'DEVICE'}}) {
			my($vname, $path, $options, $blocksize, $t10_id) = split(/\,/, $device);
			$path = cleanupString($path);
			$options =~ s/\s+//g;

			if (defined($$DEVICES{$vname}) && ($_HANDLER_MAP_{$_handler} == $$DEVICES{$vname})) {
				next;
			} elsif (defined($$DEVICES{$vname}) && ($_HANDLER_MAP_{$_handler} != $$DEVICES{$vname})) {
				if ($HANDLERS{$_HANDLER_MAP_{$_handler}}) {
					print "\t-> WARNING: Device '$vname' changes handler from '".
					  $_REVERSE_MAP_{$$DEVICES{$vname}}."' to '$_handler'.\n".
					  "\t   Use -ForceConfig to change device handler.\n" if (!$force && !$check);
				}
				next;
			}

			if ($check) {
				print "\t-> New device '$_handler:$vname' at path '$path', options '$options', ".
				  "blocksize $blocksize.\n";
				$$DEVICES{$vname} = $_HANDLER_MAP_{$_handler};
				$changes++;
			} else {
				if (addDevice($_handler, $vname, $path, $options, $blocksize)) {
					$errs++;
				} else {
					$changes++;
				}

				if ($t10_id ne '') {
					if (setT10DeviceId($_handler, $vname, $t10_id)) {
						$errs++;
					} else {
						$changes++;
					}
				}
			}
		}
	}

	# Create new groups and add users..
	foreach my $group (keys %used_users) {
		if (!defined($USERS{$group})) {
			if ($group =~ /^$_TGT_DEF_PREFIX_/) {
				my $tmp_group = randomGroup();

				if (!defined($tmp_group)) {
					print "\t-> WARNING: Unable to find a free temporary group for '$group', ".
					  "using the original group name instead.\n";

				} else {
					print "\t-> Using temporary group '$tmp_group' for group '$group'.\n";

					$rename_group{$tmp_group} = $group;
					$used_users{$tmp_group} = $used_users{$group};
					$group = $tmp_group;
				}
			}

			if ($check) {
				print "\t-> New group definition '$group.'\n";
				$GROUPS{$group}++;
				$changes++;
			} else {
				if (addGroup($group)) {
					$errs++;
				} else {
					$changes++;
				}
			}
		}

		foreach my $user (keys %{$used_users{$group}}) {
			if (!defined($USERS{$group}->{$user})) {
				my $move_group = findUserGroupInCurrent($user);
				if ($move_group) {
					print "\t-> WARNING: Use -ForceConfig to move user '$user' ".
					  "from group '$move_group' to group '$group'.\n" if (!$force);
					next;
				}

				if ($check) {
					print "\t-> New user definition '$user' for group '$group'.\n";
					$USERS{$group}->{$user}++;
					$changes++;
				} else {
					if (addUser($group, $user)) {
						$errs++;
					} else {
						$changes++;
					}
				}
			}
		}
	}

	# Assign new devices to groups..
	foreach my $group (keys %used_assignments) {
		if (!defined($GROUPS{$group})) {
			# Looks like we're lacking a group. We'll create an empty one

			print "\t-> WARNING: Auto-creating an empty group '$group' since none was configured.\n";

			if (addGroup($group)) {
				$errs++;
			} else {
				$changes++;
			}
		}

		if (!defined($GROUPS{$group})) {
			print "\t-> WARNING: Unable to assign to non-existent group '$group'.\n";
			$errs += 1;
			next;
		}

		foreach my $vname (keys %{$used_assignments{$group}}) {
			my $arg = $used_assignments{$group}->{$vname};
			my($lun, $options) = split(/\,/, $arg);
			my $_assignments = $ASSIGNMENTS{$group};

			if (defined($$_assignments{$vname}) && ($$_assignments{$vname} == $lun)) {
				next;
			} elsif (defined($$_assignments{$vname}) && ($$_assignments{$vname} != $lun)) {
				print "\t-> Device '$vname' assigned to group '$group' is at LUN ".$$_assignments{$vname}.
				  ", whereas the working configuration reflects LUN $lun.\n".
		 	          "\t   Use -ForceConfig to force this LUN change.\n" if (!$force && !$check);
			} else {
				my $replace_dev = findAssignedLun($_assignments, $lun);

				if (defined($replace_dev) && ($vname ne $replace_dev)) {
					print "\t-> WARNING: Use -ForceConfig to replace device '$replace_dev' ".
					  "with device '$vname' for group '$group'.\n" if (!$force);
					next;
				}

				if ($check) {
					$lun = 'auto' if (!defined($lun));
					print "\t-> New device assignment for '$vname' to group '$group' at LUN $lun.\n";
					$changes++;
				} else {
					if (assignDevice($group, $vname, $lun, $options)) {
						$errs++;
					} else {
						$changes++;
						$assign_changes++;
					}
				}
			}
		}
	}

	foreach my $tmp_group (keys %rename_group) {
		my $group = $rename_group{$tmp_group};
		print "\t-> Processing temporary group '$tmp_group'.\n";

		renameGroup($tmp_group, $group);
		$changes++;
	}

	# Enable/Disable configured targets
	foreach my $type (keys %{$$config{'TARGETS'}}) {
		my $enable;

		if ($type eq 'enable') {
			$enable = $TRUE;
		} elsif ($type eq 'disable') {
			$enable = $FALSE;
		} else {
			print "\t-> WARNING: Ignoring invalid TARGETS specifier '$type'. ".
			  "Should be one of enable, disable.\n";
			next;
		}

		foreach my $target (@{$$config{'TARGETS'}->{$type}->{'HOST'}}) {
			my $i_target = unformatTarget($target);

			if (!defined($$TARGETS{$i_target})) {
				print "\t-> WARNING: Target '$target' not found on system.\n";
				$errs += 1;
				next;
			}

			next if ($enable == targetEnabled($i_target));

			if (!$enable && targetEnabled($target)) {
				if ($force || $check) {
					print "\t-> WARNING: Target mode for '$target' is currently enabled, ".
					  "however configuration file wants it disabled";

					if (!$check) {
						print ", disabling.\n";
						if (enableTarget($target, $enable)) {
							$errs++;
						} else {
							$changes++;
							$targets_changed = $TRUE;
						}
					} else {
						print ".\n";
						$changes++;
					}
				}
			} else {
				print "\t-> Target '$target' is enabled in configuration file, ".
				  "however is currently disabled";

				if (!$check) {
					print ", enabling.\n";
					if (enableTarget($target, $enable)) {
						$errs++;
					} else {
						$changes++;
						$targets_changed = $TRUE;
					}
				} else {
					print ".\n";
					$changes++;
				}
			}
		}
	}

	if ($issue_lip && $assign_changes && !$targets_changed) {
		print "\nMaking initiators aware of assignment changes..\n\n";
		issueLIP();
	}

	print "\n\nEncountered $errs error(s) while processing.\n" if ($errs);

	if ($check) {
		print "\nConfiguration checked, $changes difference(s) found with working configuration.\n";
	} else {
		$changes = 0 if ($_DEBUG_);
		print "\nConfiguration applied, $changes changes made.\n";
	}

	return $TRUE if ($errs);
	return $FALSE;
}

sub clearConfiguration {
	my $noprompt = shift;
	my $errs;

	if (!$noprompt) {
		print "WARNING: This removes ALL applied SCST configuration and may result in data loss!\n";
		print "If this is not what you intend, press ctrl-c now. Waiting 10 seconds.\n\n";
		sleep 10;
	}

	print "\nRemoving all users and groups:\n\n";
	foreach my $group (keys %GROUPS) {
		$errs += removeGroup($group) if ($group ne $_DEFAULT_GROUP_);
	}

	print "\nRemoving all handler devices:\n\n";
	foreach my $device (keys %{$DEVICES}) {
		next if (!$$DEVICES{$device});
		next if ($SCST->handlerType($$DEVICES{$device}) != $SCST::SCST::IOTYPE_VIRTUAL);
		$errs += removeDevice($_REVERSE_MAP_{$$DEVICES{$device}}, $device);
	}

	print "\nEncountered $errs error(s) while processing.\n" if ($errs);
	print "\nConfiguration cleared.\n";

	return $TRUE if ($errs);
	return $FALSE;
}

sub showSessions {
	my $sessions = $SCST->sessions();
	immediateExit($SCST->errorString());

	print "\n\tTarget Name\tInitiator Name\t\t\tGroup Name\t\tCommand Count\n";

	foreach my $target (keys %{$sessions}) {
		foreach my $group (keys %{$$sessions{$target}}) {
			foreach my $user (keys %{$$sessions{$target}->{$group}}) {
				my $commands = $$sessions{$target}->{$group}->{$user};

				print "\t$target\t$user\t\t$group\t\t$commands\n";
			}
		}
	}

	print "\n";

	return $FALSE;
}

sub addDevice {
	my $handler = shift;
	my $device = shift;
	my $path = shift;
	my $options = shift;
	my $blocksize = shift;

	my $_handler = $_HANDLER_MAP_{$handler};
	my $htype = $SCST->handlerType($_handler);

	if (!$htype) {
		print "WARNING: Internal error occurred: ".$SCST->errorString()."\n";
		return $TRUE;
	}

	if ($htype != $SCST::SCST::IOTYPE_VIRTUAL) {
		my $typeString = $_HANDLER_TYPE_MAP_{$htype};
		my $validType = $_HANDLER_TYPE_MAP_{$SCST::SCST::IOTYPE_VIRTUAL};
		print "WARNING: Handler $handler of type $typeString is incapable of ".
		  "opening/closing devices. Valid handlers are:\n".
		  validHandlerTypes($SCST::SCST::IOTYPE_VIRTUAL)."\n";
		return $TRUE;
	}

	if (defined($$DEVICES{$device})) {
		print "WARNING: Device '$device' already defined.\n";
		return $TRUE;
	}

	print "\t-> Opening virtual device '$device' at path '$path' using handler '$handler'..\n";

	if ($SCST->openDevice($_handler, $device, $path, $options, $blocksize)) {
		print "WARNING: Failed to open virtual device '$device' at path '$path': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	$$DEVICES{$device} = $_handler;

	return $FALSE;
}

sub resyncDevice {
	my $handler = shift;
	my $device = shift;

	my $_handler = $_HANDLER_MAP_{$handler};
	my $htype = $SCST->handlerType($_handler);

	if (!defined($$DEVICES{$device})) {
		print "WARNING: Device '$device' not defined.\n";
		return $TRUE;
	}

	print "\t-> Resync'ing virtual device '$device'..\n";

	if ($SCST->resyncDevice($_handler, $device)) {
		print "WARNING: Failed to resync virtual device '$device': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	return $FALSE;
}

sub removeDevice {
	my $handler = shift;
	my $device = shift;

	my $_handler = $_HANDLER_MAP_{$handler};
	my $htype = $SCST->handlerType($_handler);

	if (!$htype) {
		print "WARNING: Internal error occurred: ".$SCST->errorString()."\n";
		return $TRUE;
	}

	if ($htype != $SCST::SCST::IOTYPE_VIRTUAL) {
		my $typeString = $_HANDLER_TYPE_MAP_{$htype};
		my $validType = $_HANDLER_TYPE_MAP_{$SCST::SCST::IOTYPE_VIRTUAL};
		print "WARNING: Handler $handler of type $typeString is incapable of ".
		  "opening/closing devices. Valid handlers are:\n".
		  validHandlerTypes($SCST::SCST::IOTYPE_VIRTUAL)."\n";
		return $TRUE;
	}

	if (!defined($$DEVICES{$device})) {
		print "WARNING: Device '$device' not defined.\n";
		return $TRUE;
	}

	print "\t-> Closing virtual device '$device'..\n";

	if ($SCST->closeDevice($_handler, $device)) {
		print "WARNING: Failed to close virtual device '$device': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	undef $$DEVICES{$device};

	return $FALSE;
}

sub setT10DeviceId {
	my $handler = shift;
	my $device = shift;
	my $t10_id = shift;

	my $_handler = $_HANDLER_MAP_{$handler};
	my $htype = $SCST->handlerType($_handler);

	if (!defined($$DEVICES{$device})) {
		print "WARNING: Device '$device' not defined.\n";
		return $TRUE;
	}

	print "\t-> Changing T10 Device ID of virtual device '$device' to '$t10_id'..\n";

	if ($SCST->setT10DeviceId($_handler, $device, $t10_id)) {
		print "WARNING: Failed to changing T10 Device ID of virtual device '$device': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	return $FALSE;
}

sub addGroup {
	my $group = shift;

	if (defined($GROUPS{$group})) {
		print "WARNING: Group '$group' already exists.\n";
		return $TRUE;
	}

	print "\t-> Creating security group '$group'..\n";

	if ($SCST->addGroup($group)) {
		print "WARNING: Failed to create security group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	$GROUPS{$group}++;

	return $FALSE;
}

sub renameGroup {
	my $group = shift;
	my $toGroup = shift;

	if (defined($GROUPS{$toGroup})) {
		print "WARNING: Group '$toGroup' already exists.\n";
		return $TRUE;
	}

	print "\t-> Renaming security group '$group' to '$toGroup'..\n";

	if ($SCST->renameGroup($group, $toGroup)) {
		print "WARNING: Failed to rename security group '$group' to ".
		  "'$toGroup': ".$SCST->errorString()."\n";
		return $TRUE;
	}

	delete $GROUPS{$group};
	$GROUPS{$toGroup}++;

	return $FALSE;
}

sub removeGroup {
	my $group = shift;

	return $FALSE if ($group eq $_DEFAULT_GROUP_);

	if (!defined($GROUPS{$group})) {
		print "WARNING: Group '$group' does not exist.\n";
		return $TRUE;
	}

	print "\t-> Removing security group '$group'..\n";

	if ($SCST->removeGroup($group)) {
		print "WARNING: Failed to remove security group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	undef $GROUPS{$group};

	return $FALSE;
}

sub addUser {
	my $group = shift;
	my $user = shift;

	if (!defined($GROUPS{$group})) {
		print "WARNING: Failed to add user '$user' to group '$group', group does not exist.\n";
		return $TRUE;
	}

	if (defined($USERS{$group}->{$user})) {
		print "WARNING: User '$user' already exists in security group '$group'.\n";
		return $TRUE;
	}

	print "\t-> Adding user '$user' to security group '$group'..\n";

	if ($SCST->addUser($user, $group)) {
		print "WARNING: Failed to add user '$user' to security group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	$USERS{$group}->{$user}++;

	return $FALSE;
}

sub moveUser {
	my $group = shift;
	my $user = shift;
	my $toGroup = shift;

	if (!defined($GROUPS{$group})) {
		print "WARNING: Failed to move user '$user' from group '$group', group does not exist.\n";
		return $TRUE;
	}

	if (defined($USERS{$toGroup}->{$user})) {
		print "WARNING: User '$user' already exists in security group '$toGroup'.\n";
		return $TRUE;
	}

	print "\t-> Moving user '$user' from security group '$group' to security group '$toGroup'..\n";

	if ($SCST->moveUser($user, $group, $toGroup)) {
		print "WARNING: Failed to move user '$user' from security group '$group' to ".
		  "security group '$toGroup': ".$SCST->errorString()."\n";
		return $TRUE;
	}

	delete $USERS{$group}->{$user};
	$USERS{$toGroup}->{$user}++;

	return $FALSE;
}

sub removeUser {
	my $group = shift;
	my $user = shift;

	if (!defined($GROUPS{$group})) {
		print "WARNING: Failed to remove user '$user' from group '$group', group does not exist.\n";
		return $TRUE;
	}

	if (!defined($USERS{$group}->{$user})) {
		print "WARNING: User '$user' doesn\'t exist in security group '$group'.\n";
		return $TRUE;
	}

	print "\t-> Removing user '$user' from security group '$group'..\n";

	if ($SCST->removeUser($user, $group)) {
		print "WARNING: Failed to remove user '$user' to security group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	undef $USERS{$group}->{$user};

	return $FALSE;
}

sub clearUsers {
	my $group = shift;

	if (!defined($GROUPS{$group})) {
		print "WARNING: Failed to clear users from group '$group', group does not exist.\n";
		return $TRUE;
	}

	print "\t-> Clearing users from security group '$group'..\n";

	if ($SCST->clearUsers($group)) {
		print "WARNING: Failed to clear users from security group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	undef $USERS{$group};

	return $FALSE;
}

sub assignDevice {
	my $group = shift;
	my $device = shift;
	my $lun = shift;
	my $options = shift;
	my %allLuns;

	# Put luns into something easier to parse..
	foreach my $_group (keys %ASSIGNMENTS) {
		my $_gAssigns = $ASSIGNMENTS{$_group};

		foreach my $_device (keys %{$_gAssigns}) {
			@{$allLuns{$_group}}[$$_gAssigns{$_device}] = $_device;
		}
	}

	# Use the next available LUN if none specified
	if ($lun !~ /\d+/) {
		$lun = ($#{$allLuns{$group}} + 1);
		if ($lun > $_MAX_LUNS_) {
			print "ERROR: Unable to use next available LUN of $lun, lun out of range.\n";
			return $TRUE;
		}

		print "\t-> Device '$device': Using next available LUN of $lun for group '$group'.\n";
	}
			
	if (($lun < 0) || ($lun > $_MAX_LUNS_)) {
		print "ERROR: Unable to assign device '$device', lun '$lun' is out of range.\n";
		return $TRUE;
	}

	if (!defined($$DEVICES{$device})) {
		print "WARNING: Unable to assign non-existent device '$device' to group '$group'.\n";
		return $TRUE;
	}

	if (@{$allLuns{$group}}[$lun]) {
		print "ERROR: Device '$device': Lun '$lun' is already assigned to device '".@{$allLuns{$group}}[$lun]."'.\n";
		return $TRUE;
	}

	print "\t-> Assign virtual device '$device' to group '$group' at LUN '$lun'..\n";

	if ($SCST->assignDeviceToGroup($device, $group, $lun, $options)) {
		print "WARNING: Failed to assign device '$device' to group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	if (!defined($ASSIGNMENTS{$group})) {
		my %assignments_t;
		$ASSIGNMENTS{$group} = \%assignments_t;
	}

	my $_assignments = $ASSIGNMENTS{$group};

	$$_assignments{$device} = $lun; 

	return $FALSE;
}

sub replaceDevice {
	my $group = shift;
	my $newDevice = shift;
	my $lun = shift;
	my $options = shift;
	my %allLuns;

	# Put luns into something easier to parse..
	foreach my $_group (keys %ASSIGNMENTS) {
		my $_gAssigns = $ASSIGNMENTS{$_group};

		foreach my $_device (keys %{$_gAssigns}) {
			@{$allLuns{$_group}}[$$_gAssigns{$_device}] = $_device;
		}
	}
			
	if (!defined($$DEVICES{$newDevice})) {
		print "WARNING: Unable to assign non-existent device '$newDevice' to group '$group'.\n";
		return $TRUE;
	}

	if (${$allLuns{$group}}[$lun] eq $newDevice) {
		print "ERROR: Device '$newDevice': Lun '$lun' is already assigned to device '$newDevice'.\n";
		return $TRUE;
	}

	print "\t-> Replace device at LUN '$lun' in group '$group' with new device '$newDevice'..\n";

	if ($SCST->replaceDeviceInGroup($newDevice, $group, $lun, $options)) {
		print "WARNING: Failed to replace LUN '$lun' in group '$group' with new device '$newDevice': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	if (!defined($ASSIGNMENTS{$group})) {
		my %assignments_t;
		$ASSIGNMENTS{$group} = \%assignments_t;
	}

	my $_assignments = $ASSIGNMENTS{$group};

	delete $$_assignments{${$allLuns{$group}}[$lun]};
	$$_assignments{$newDevice} = $lun;

	return $FALSE;
}

sub releaseDevice {
	my $group = shift;
	my $device = shift;

	if (!defined($GROUPS{$group})) {
		print "WARNING: Failed to release device '$device' from group '$group', group does not exist.\n";
		return $TRUE;
	}

	if (!defined($$DEVICES{$device})) {
		print "WARNING: Failed to release device '$device', device not defined.\n";
		return $TRUE;
	}

	print "\t-> Release virtual device '$device' from group '$group'..\n";

	if ($SCST->removeDeviceFromGroup($device, $group)) {
		print "WARNING: Failed to release device '$device' from group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	my $_assignments = $ASSIGNMENTS{$group};

	undef $$_assignments{$device};

	return $FALSE;
}

sub clearDevices {
	my $group = shift;

	if (!defined($GROUPS{$group})) {
		print "WARNING: Failed to clear devices from group '$group', group does not exist.\n";
		return $TRUE;
	}

	print "\t-> Clear virtual devices from group '$group'..\n";

	if ($SCST->clearGroupDevices($group)) {
		print "WARNING: Failed to clear devices from group '$group': ".
		  $SCST->errorString()."\n";
		return $TRUE;
	}

	undef $ASSIGNMENTS{$group};

	return $FALSE;
}

sub targets {
	my %targets;
	my %fcards;

	my $root = new IO::Dir $_FC_CLASS_ if (-d $_FC_CLASS_);

	if ($root) {
		while (my $entry = $root->read()) {
			next if (($entry eq '.') || ($entry eq '..'));

			my $io = new IO::File "$_FC_CLASS_/$entry/port_name", O_RDONLY;

			if ($io) {
				my $wwn = <$io>;
				chomp $wwn;
				close $io;

				$fcards{$entry} = $wwn;
			}
		}
	}

	$root = new IO::Dir $_SCSI_CLASS_ if (-d $_SCSI_CLASS_);

	if ($root) {
		while (my $entry = $root->read()) {
			next if (($entry eq '.') || ($entry eq '..'));

			my $io = new IO::File "$_SCSI_CLASS_/$entry/target_mode_enabled", O_RDONLY;

			if ($io) {
				my $enabled = <$io>;
				chomp $enabled;
				close $io;

				$targets{$entry}->{'path'} = "$_SCSI_CLASS_/$entry/target_mode_enabled";
				$targets{$entry}->{'enabled'} = $enabled;
				$targets{$entry}->{'qla_isp'} = $FALSE;

				if ($fcards{$entry}) {
					$targets{$fcards{$entry}}->{'enabled'} = $enabled;
					$targets{$fcards{$entry}}->{'path'} =
					  "$_SCSI_CLASS_/$entry/target_mode_enabled";

					if (-f "$_FC_CLASS_/$entry/issue_lip") {
						$targets{$fcards{$entry}}->{'ilip'} = "$_FC_CLASS_/$entry/issue_lip";
					}

					$targets{$entry}->{'duplicate'} = $TRUE;
				} else {
					$targets{$entry}->{'duplicate'} = $FALSE;
				}
			}
		}
	}

	$root = new IO::Dir $_SCSI_ISP_ if (-d $_SCSI_ISP_);

	if ($root) {
		while (my $entry = $root->read()) {
			next if (($entry eq '.') || ($entry eq '..'));

			local $/;
			my $io = new IO::File "$_SCSI_ISP_/$entry", O_RDONLY;

			if ($io) {
				my $wwn;
				my $fstr;
				my $enabled2;

				$fstr = <$io>;
				close $io;

				($wwn) = ($fstr =~ '.*?Port WWN +([^\ ]+) .*');
				$fcards{$entry} = $wwn;

				$io = new IO::File "$_SCSITGT_QLAISP_/$entry", O_RDONLY;
				if ($io) {
					$fstr = <$io>;
					close $io;

					($enabled2) = ($fstr =~ '[^\n]+\n *\d *: *(\d)');
					$targets{$entry}->{'path'} = "$_SCSITGT_QLAISP_/$entry";
					$targets{$entry}->{'enabled'} = $enabled2;
					$targets{$entry}->{'qla_isp'} = $TRUE;

					if ($fcards{$entry}) {
						$targets{$fcards{$entry}}->{'enabled'} = $enabled2;
			    			$targets{$fcards{$entry}}->{'path'} = "$_SCSITGT_QLAISP_/$entry";
						$targets{$fcards{$entry}}->{'qla_isp'} = $TRUE;
						$targets{$entry}->{'duplicate'} = $TRUE;
    					} else {
						$targets{$entry}->{'duplicate'} = $FALSE;
					}
				}
			}
		}
	}

	return \%targets;
}

sub targetEnabled {
	my $target = shift;

	return undef if (!defined($$TARGETS{$target}));
	return $$TARGETS{$target}->{'enabled'};
}

sub enableTarget {
	my $target = shift;
	my $enable = shift;

	$target = unformatTarget($target);

	return undef if (!defined($$TARGETS{$target}));

	my $io = new IO::File $$TARGETS{$target}->{'path'}, O_WRONLY;
	return $TRUE if (!$io);

	print $enable ? "\t-> Enabling" : "\t-> Disabling";
	print " target mode for SCST host '$target'.\n";

	if ($_DEBUG_) {
		print "DBG($$): ".$$TARGETS{$target}->{'path'}." -> $enable\n\n";
	} else {
		if ($$TARGETS{$target}->{'qla_isp'} == $FALSE) {
			print $io $enable;
		} else {
			print $io $enable ? "enable all" : "disable all";
		}
	}

	close $io;

	$$TARGETS{$target}->{'enabled'} = $enable;

	return $FALSE;
}

sub issueLIP {
	my $lip_issued;

	foreach my $target (keys %{$TARGETS}) {
		next if ($$TARGETS{$target}->{'duplicate'});
		next if (!$$TARGETS{$target}->{'enabled'});

		if (defined($$TARGETS{$target}->{'ilip'})) {
			my $io = new IO::File $$TARGETS{$target}->{'ilip'}, O_WRONLY;
			return $TRUE if (!$io);

			print "\t-> Issuing LIP for target '".formatTarget($target)."': ";

			if ($_DEBUG_) {
				print "DBG($$): ".$$TARGETS{$target}->{'ilip'}." -> $TRUE\n\n";
			} else {
				print $io $TRUE;
			}

			print "done.\n";

			$lip_issued = $TRUE;
		}
	}

	print "\t-> WARNING: This target driver does not support issuing LIPs.\n\n"
	  if (!$lip_issued);
}

sub readConfig {
	my $confile = shift;
	my $ignoreError = shift;
	my %config;
	my $section;
	my $last_section;
	my $arg;
	my $last_arg;
	my %empty;

	my $io = new IO::File $confile, O_RDONLY;

	if (!$io) {
		return undef if ($ignoreError);

		die("FATAL: Unable to open specified configuration file $confile: $!\n");
	}

	while (my $line = <$io>) {
		($line, undef) = split(/\#/, $line, 2);
		$line = cleanupString($line);

		if ($line =~ /^\[(.*)\]$/) {
			($section, $arg) = split(/\s+/, $1, 2);

			$arg = 'default' if ($section eq 'OPTIONS');

			if ($last_arg && ($last_arg ne $arg) &&
			    !defined($config{$last_section}->{$last_arg})) {
				$config{$last_section}->{$last_arg} = \%empty;
			}   

			$last_arg = $arg;
			$last_section = $section;
		} elsif ($section && $arg && $line) {
			my($parameter, $value) = split(/\s+/, $line, 2);

			if ($section eq 'OPTIONS') {
				$value = $TRUE if (($value == 1) ||
				                   ($value =~ /^TRUE$/i) ||
						   ($value =~ /^YES$/i));
				$value = $FALSE if (($value == 0) ||
						    ($value =~ /^FALSE$/i) ||
						    ($value =~ /^NO$/i));
			}

			push @{$config{$section}->{$arg}->{$parameter}}, $value;
		}
	}

	close $io;	

	return \%config;
}

sub findUserGroup {
	my $user = shift;
	my $config = shift;

	foreach my $group (keys %{$$config{'GROUP'}}) {
		foreach my $_user (@{$$config{'GROUP'}->{$group}->{'USER'}}) {
			return $group if ($_user eq $user);
		}
	}

	return undef;
}

sub findUserGroupInCurrent {
	my $user = shift;

	foreach my $group (keys %USERS) {
		foreach my $_user (keys %{$USERS{$group}}) {
			return $group if ($_user eq $user);
		}
	}

	return undef;
}

sub findAssignedLun {
	my $associations = shift;
	my $lun = shift;

	return undef if (!defined($lun));

	foreach my $device (keys %{$associations}) {
		if ($$associations{$device} == $lun) {
			return $device;
		}
	}

	return undef;
}

sub cleanupString {
	my $string = shift;

	$string =~ s/^\s+//;
	$string =~ s/\s+$//;

	return $string;
}

sub formatTarget {
	my $target = shift;

	if ($target =~ /^0x/) {
		$target =~ s/^0x//;
		my($o1, $o2, $o3, $o4, $o5, $o6, $o7, $o8) = unpack("A2A2A2A2A2A2A2A2", $target);
		$target = "$o1:$o2:$o3:$o4:$o5:$o6:$o7:$o8";
	}

	$target =~ tr/A-Z/a-z/;

	return $target;
}

sub unformatTarget {
	my $target = shift;

	if ($target =~ /^.{2}\:.{2}\:.{2}\:.{2}\:.{2}\:.{2}\:.{2}\:.{2}/) {
		$target =~ s/\://g;
		$target = "0x$target";
	}

	$target =~ tr/A-Z/a-z/;

	return $target;
}

sub validHandlerTypes {
	my $type = shift;
	my $buffer = "\n";

	foreach my $handler (keys %_REVERSE_MAP_) {
		$buffer .= "\t".$_REVERSE_MAP_{$handler}."\n" if ($SCST->handlerType($handler) == $type);
	}

	return $buffer;
}

sub randomGroup {
	my $tmp_group;
	my $retry = 0;

	while (($retry < 10000) && (!$tmp_group || $SCST->groupExists($tmp_group))) {
		my $id = int(rand(10000));

		$tmp_group = $_TGT_TMP_PREFIX_.$id;

		$retry++;
	}

	return undef if ($SCST->groupExists($tmp_group));
	return $tmp_group;
}

# If we have an unread error from SCST, exit immediately
sub immediateExit {
	my $error = shift;

	return if (!$error);

	print "\n\nFATAL: Received the following error:\n\n\t";
	print "$error\n\n";

	exit 1;
}

# Hey! Stop that!
sub commitSuicide {
	print "\n\nAborting immediately.\n";
	exit 1;
}
